use std::cell::Ref;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use uuid::Uuid;
use yew::Callback;

pub trait Update<S> {
    type Event: Clone;
    fn update(self, state: &mut S) -> Option<Self::Event>;
}

pub struct StoreLink<S, U: Update<S>> {
    store: Rc<Store<S, U>>,
    register_id: Option<Uuid>,
}

impl<S, U: Update<S>> Drop for StoreLink<S, U> {
    fn drop(&mut self) {
        self.register_id.clone().map(|id| self.store.unregister(id));
    }
}

impl<S, U: Update<S>> Clone for StoreLink<S, U> {
    fn clone(&self) -> Self {
        StoreLink {
            store: Rc::clone(&self.store),
            register_id: None,
        }
    }
}

impl<S, U: Update<S>> StoreLink<S, U> {
    pub fn reform(mut self, callback: Option<Callback<U::Event>>) -> StoreLink<S, U> {
        self.register_id = callback.map(|c| self.store.register(c));
        self
    }

    pub fn state(&self) -> Ref<S> {
        self.store.state.borrow()
    }

    pub fn send(&mut self, action: U) {
        action
            .update(&mut *self.store.state.borrow_mut())
            .map(|event| self.store.notify(event));
    }
}

pub fn create_store<S, U: Update<S>>(state: S) -> StoreLink<S, U> {
    let store = Rc::new(Store::new_with(state));
    StoreLink {
        store,
        register_id: None,
    }
}

pub fn create_store_with_callback<S, U: Update<S>>(
    state: S,
    callback: Option<Callback<U::Event>>,
) -> StoreLink<S, U> {
    create_store(state).reform(callback)
}

struct Store<S, U: Update<S>> {
    state: RefCell<S>,
    observers: RefCell<HashMap<Uuid, Callback<U::Event>>>,
}

impl<S, U: Update<S>> Store<S, U> {
    fn new_with(state: S) -> Self {
        Store {
            state: RefCell::new(state),
            observers: RefCell::new(HashMap::new()),
        }
    }

    fn register(&self, callback: Callback<U::Event>) -> Uuid {
        let uuid = Uuid::new_v4();
        self.observers.borrow_mut().insert(uuid, callback);
        uuid
    }

    fn notify(&self, event: U::Event) {
        self.observers
            .borrow_mut()
            .iter_mut()
            .for_each(|(_, callback)| callback.emit(event.clone()));
    }
    fn unregister(&self, uuid: Uuid) {
        self.observers.borrow_mut().remove(&uuid);
    }
}
